1   /*
2    * Copyright (c) 2000, 2008, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  
26  package javax.swing.text.html;
27  
28  import java.awt.*;
29  import java.awt.event.*;
30  import java.beans.*;
31  import java.util.*;
32  import javax.swing.*;
33  import javax.swing.event.*;
34  import javax.swing.text.*;
35  import javax.accessibility.*;
36  import java.text.BreakIterator;
37  
38  /*
39   * The AccessibleHTML class provide information about the contents
40   * of a HTML document to assistive technologies.
41   *
42   * @author  Lynn Monsanto
43   */
44  class AccessibleHTML implements Accessible {
45  
46      /**
47       * The editor.
48       */
49      private JEditorPane editor;
50      /**
51       * Current model.
52       */
53      private Document model;
54      /**
55       * DocumentListener installed on the current model.
56       */
57      private DocumentListener docListener;
58      /**
59       * PropertyChangeListener installed on the editor
60       */
61      private PropertyChangeListener propChangeListener;
62      /**
63       * The root ElementInfo for the document
64       */
65      private ElementInfo rootElementInfo;
66      /*
67       * The root accessible context for the document
68       */
69      private RootHTMLAccessibleContext rootHTMLAccessibleContext;
70  
71      public AccessibleHTML(JEditorPane pane) {
72          editor = pane;
73          propChangeListener = new PropertyChangeHandler();
74          setDocument(editor.getDocument());
75  
76          docListener = new DocumentHandler();
77      }
78  
79      /**
80       * Sets the document.
81       */
82      private void setDocument(Document document) {
83          if (model != null) {
84              model.removeDocumentListener(docListener);
85          }
86          if (editor != null) {
87              editor.removePropertyChangeListener(propChangeListener);
88          }
89          this.model = document;
90          if (model != null) {
91              if (rootElementInfo != null) {
92                  rootElementInfo.invalidate(false);
93              }
94              buildInfo();
95              model.addDocumentListener(docListener);
96          }
97          else {
98              rootElementInfo = null;
99          }
100         if (editor != null) {
101             editor.addPropertyChangeListener(propChangeListener);
102         }
103     }
104 
105     /**
106      * Returns the Document currently presenting information for.
107      */
108     private Document getDocument() {
109         return model;
110     }
111 
112     /**
113      * Returns the JEditorPane providing information for.
114      */
115     private JEditorPane getTextComponent() {
116         return editor;
117     }
118 
119     /**
120      * Returns the ElementInfo representing the root Element.
121      */
122     private ElementInfo getRootInfo() {
123         return rootElementInfo;
124     }
125 
126     /**
127      * Returns the root <code>View</code> associated with the current text
128      * component.
129      */
130     private View getRootView() {
131         return getTextComponent().getUI().getRootView(getTextComponent());
132     }
133 
134     /**
135      * Returns the bounds the root View will be rendered in.
136      */
137     private Rectangle getRootEditorRect() {
138         Rectangle alloc = getTextComponent().getBounds();
139         if ((alloc.width > 0) && (alloc.height > 0)) {
140             alloc.x = alloc.y = 0;
141             Insets insets = editor.getInsets();
142             alloc.x += insets.left;
143             alloc.y += insets.top;
144             alloc.width -= insets.left + insets.right;
145             alloc.height -= insets.top + insets.bottom;
146             return alloc;
147         }
148         return null;
149     }
150 
151     /**
152      * If possible acquires a lock on the Document.  If a lock has been
153      * obtained a key will be retured that should be passed to
154      * <code>unlock</code>.
155      */
156     private Object lock() {
157         Document document = getDocument();
158 
159         if (document instanceof AbstractDocument) {
160             ((AbstractDocument)document).readLock();
161             return document;
162         }
163         return null;
164     }
165 
166     /**
167      * Releases a lock previously obtained via <code>lock</code>.
168      */
169     private void unlock(Object key) {
170         if (key != null) {
171             ((AbstractDocument)key).readUnlock();
172         }
173     }
174 
175     /**
176      * Rebuilds the information from the current info.
177      */
178     private void buildInfo() {
179         Object lock = lock();
180 
181         try {
182             Document doc = getDocument();
183             Element root = doc.getDefaultRootElement();
184 
185             rootElementInfo = new ElementInfo(root);
186             rootElementInfo.validate();
187         } finally {
188             unlock(lock);
189         }
190     }
191 
192     /*
193      * Create an ElementInfo subclass based on the passed in Element.
194      */
195     ElementInfo createElementInfo(Element e, ElementInfo parent) {
196         AttributeSet attrs = e.getAttributes();
197 
198         if (attrs != null) {
199             Object name = attrs.getAttribute(StyleConstants.NameAttribute);
200 
201             if (name == HTML.Tag.IMG) {
202                 return new IconElementInfo(e, parent);
203             }
204             else if (name == HTML.Tag.CONTENT || name == HTML.Tag.CAPTION) {
205                 return new TextElementInfo(e, parent);
206             }
207             else if (name == HTML.Tag.TABLE) {
208                 return new TableElementInfo(e, parent);
209             }
210         }
211         return null;
212     }
213 
214     /**
215      * Returns the root AccessibleContext for the document
216      */
217     public AccessibleContext getAccessibleContext() {
218         if (rootHTMLAccessibleContext == null) {
219             rootHTMLAccessibleContext =
220                 new RootHTMLAccessibleContext(rootElementInfo);
221         }
222         return rootHTMLAccessibleContext;
223     }
224 
225     /*
226      * The roow AccessibleContext for the document
227      */
228     private class RootHTMLAccessibleContext extends HTMLAccessibleContext {
229 
230         public RootHTMLAccessibleContext(ElementInfo elementInfo) {
231             super(elementInfo);
232         }
233 
234         /**
235          * Gets the accessibleName property of this object.  The accessibleName
236          * property of an object is a localized String that designates the purpose
237          * of the object.  For example, the accessibleName property of a label
238          * or button might be the text of the label or button itself.  In the
239          * case of an object that doesn't display its name, the accessibleName
240          * should still be set.  For example, in the case of a text field used
241          * to enter the name of a city, the accessibleName for the en_US locale
242          * could be 'city.'
243          *
244          * @return the localized name of the object; null if this
245          * object does not have a name
246          *
247          * @see #setAccessibleName
248          */
249         public String getAccessibleName() {
250             if (model != null) {
251                 return (String)model.getProperty(Document.TitleProperty);
252             } else {
253                 return null;
254             }
255         }
256 
257         /**
258          * Gets the accessibleDescription property of this object.  If this
259          * property isn't set, returns the content type of this
260          * <code>JEditorPane</code> instead (e.g. "plain/text", "html/text").
261          *
262          * @return the localized description of the object; <code>null</code>
263          *      if this object does not have a description
264          *
265          * @see #setAccessibleName
266          */
267         public String getAccessibleDescription() {
268             return editor.getContentType();
269         }
270 
271         /**
272          * Gets the role of this object.  The role of the object is the generic
273          * purpose or use of the class of this object.  For example, the role
274          * of a push button is AccessibleRole.PUSH_BUTTON.  The roles in
275          * AccessibleRole are provided so component developers can pick from
276          * a set of predefined roles.  This enables assistive technologies to
277          * provide a consistent interface to various tweaked subclasses of
278          * components (e.g., use AccessibleRole.PUSH_BUTTON for all components
279          * that act like a push button) as well as distinguish between sublasses
280          * that behave differently (e.g., AccessibleRole.CHECK_BOX for check boxes
281          * and AccessibleRole.RADIO_BUTTON for radio buttons).
282          * <p>Note that the AccessibleRole class is also extensible, so
283          * custom component developers can define their own AccessibleRole's
284          * if the set of predefined roles is inadequate.
285          *
286          * @return an instance of AccessibleRole describing the role of the object
287          * @see AccessibleRole
288          */
289         public AccessibleRole getAccessibleRole() {
290             return AccessibleRole.TEXT;
291         }
292     }
293 
294     /*
295      * Base AccessibleContext class for HTML elements
296      */
297     protected abstract class HTMLAccessibleContext extends AccessibleContext
298         implements Accessible, AccessibleComponent {
299 
300         protected ElementInfo elementInfo;
301 
302         public HTMLAccessibleContext(ElementInfo elementInfo) {
303             this.elementInfo = elementInfo;
304         }
305 
306         // begin AccessibleContext implementation ...
307         public AccessibleContext getAccessibleContext() {
308             return this;
309         }
310 
311         /**
312          * Gets the state set of this object.
313          *
314          * @return an instance of AccessibleStateSet describing the states
315          * of the object
316          * @see AccessibleStateSet
317          */
318         public AccessibleStateSet getAccessibleStateSet() {
319             AccessibleStateSet states = new AccessibleStateSet();
320             Component comp = getTextComponent();
321 
322             if (comp.isEnabled()) {
323                 states.add(AccessibleState.ENABLED);
324             }
325             if (comp instanceof JTextComponent &&
326                 ((JTextComponent)comp).isEditable()) {
327 
328                 states.add(AccessibleState.EDITABLE);
329                 states.add(AccessibleState.FOCUSABLE);
330             }
331             if (comp.isVisible()) {
332                 states.add(AccessibleState.VISIBLE);
333             }
334             if (comp.isShowing()) {
335                 states.add(AccessibleState.SHOWING);
336             }
337             return states;
338         }
339 
340         /**
341          * Gets the 0-based index of this object in its accessible parent.
342          *
343          * @return the 0-based index of this object in its parent; -1 if this
344          * object does not have an accessible parent.
345          *
346          * @see #getAccessibleParent
347          * @see #getAccessibleChildrenCount
348          * @see #getAccessibleChild
349          */
350         public int getAccessibleIndexInParent() {
351             return elementInfo.getIndexInParent();
352         }
353 
354         /**
355          * Returns the number of accessible children of the object.
356          *
357          * @return the number of accessible children of the object.
358          */
359         public int getAccessibleChildrenCount() {
360             return elementInfo.getChildCount();
361         }
362 
363         /**
364          * Returns the specified Accessible child of the object.  The Accessible
365          * children of an Accessible object are zero-based, so the first child
366          * of an Accessible child is at index 0, the second child is at index 1,
367          * and so on.
368          *
369          * @param i zero-based index of child
370          * @return the Accessible child of the object
371          * @see #getAccessibleChildrenCount
372          */
373         public Accessible getAccessibleChild(int i) {
374             ElementInfo childInfo = elementInfo.getChild(i);
375             if (childInfo != null && childInfo instanceof Accessible) {
376                 return (Accessible)childInfo;
377             } else {
378                 return null;
379             }
380         }
381 
382         /**
383          * Gets the locale of the component. If the component does not have a
384          * locale, then the locale of its parent is returned.
385          *
386          * @return this component's locale.  If this component does not have
387          * a locale, the locale of its parent is returned.
388          *
389          * @exception IllegalComponentStateException
390          * If the Component does not have its own locale and has not yet been
391          * added to a containment hierarchy such that the locale can be
392          * determined from the containing parent.
393          */
394         public Locale getLocale() throws IllegalComponentStateException {
395             return editor.getLocale();
396         }
397         // ... end AccessibleContext implementation
398 
399         // begin AccessibleComponent implementation ...
400         public AccessibleComponent getAccessibleComponent() {
401             return this;
402         }
403 
404         /**
405          * Gets the background color of this object.
406          *
407          * @return the background color, if supported, of the object;
408          * otherwise, null
409          * @see #setBackground
410          */
411         public Color getBackground() {
412             return getTextComponent().getBackground();
413         }
414 
415         /**
416          * Sets the background color of this object.
417          *
418          * @param c the new Color for the background
419          * @see #setBackground
420          */
421         public void setBackground(Color c) {
422             getTextComponent().setBackground(c);
423         }
424 
425         /**
426          * Gets the foreground color of this object.
427          *
428          * @return the foreground color, if supported, of the object;
429          * otherwise, null
430          * @see #setForeground
431          */
432         public Color getForeground() {
433             return getTextComponent().getForeground();
434         }
435 
436         /**
437          * Sets the foreground color of this object.
438          *
439          * @param c the new Color for the foreground
440          * @see #getForeground
441          */
442         public void setForeground(Color c) {
443             getTextComponent().setForeground(c);
444         }
445 
446         /**
447          * Gets the Cursor of this object.
448          *
449          * @return the Cursor, if supported, of the object; otherwise, null
450          * @see #setCursor
451          */
452         public Cursor getCursor() {
453             return getTextComponent().getCursor();
454         }
455 
456         /**
457          * Sets the Cursor of this object.
458          *
459          * @param cursor the new Cursor for the object
460          * @see #getCursor
461          */
462         public void setCursor(Cursor cursor) {
463             getTextComponent().setCursor(cursor);
464         }
465 
466         /**
467          * Gets the Font of this object.
468          *
469          * @return the Font,if supported, for the object; otherwise, null
470          * @see #setFont
471          */
472         public Font getFont() {
473             return getTextComponent().getFont();
474         }
475 
476         /**
477          * Sets the Font of this object.
478          *
479          * @param f the new Font for the object
480          * @see #getFont
481          */
482         public void setFont(Font f) {
483             getTextComponent().setFont(f);
484         }
485 
486         /**
487          * Gets the FontMetrics of this object.
488          *
489          * @param f the Font
490          * @return the FontMetrics, if supported, the object; otherwise, null
491          * @see #getFont
492          */
493         public FontMetrics getFontMetrics(Font f) {
494             return getTextComponent().getFontMetrics(f);
495         }
496 
497         /**
498          * Determines if the object is enabled.  Objects that are enabled
499          * will also have the AccessibleState.ENABLED state set in their
500          * AccessibleStateSets.
501          *
502          * @return true if object is enabled; otherwise, false
503          * @see #setEnabled
504          * @see AccessibleContext#getAccessibleStateSet
505          * @see AccessibleState#ENABLED
506          * @see AccessibleStateSet
507          */
508         public boolean isEnabled() {
509             return getTextComponent().isEnabled();
510         }
511 
512         /**
513          * Sets the enabled state of the object.
514          *
515          * @param b if true, enables this object; otherwise, disables it
516          * @see #isEnabled
517          */
518         public void setEnabled(boolean b) {
519             getTextComponent().setEnabled(b);
520         }
521 
522         /**
523          * Determines if the object is visible.  Note: this means that the
524          * object intends to be visible; however, it may not be
525          * showing on the screen because one of the objects that this object
526          * is contained by is currently not visible.  To determine if an object
527          * is showing on the screen, use isShowing().
528          * <p>Objects that are visible will also have the
529          * AccessibleState.VISIBLE state set in their AccessibleStateSets.
530          *
531          * @return true if object is visible; otherwise, false
532          * @see #setVisible
533          * @see AccessibleContext#getAccessibleStateSet
534          * @see AccessibleState#VISIBLE
535          * @see AccessibleStateSet
536          */
537         public boolean isVisible() {
538             return getTextComponent().isVisible();
539         }
540 
541         /**
542          * Sets the visible state of the object.
543          *
544          * @param b if true, shows this object; otherwise, hides it
545          * @see #isVisible
546          */
547         public void setVisible(boolean b) {
548             getTextComponent().setVisible(b);
549         }
550 
551         /**
552          * Determines if the object is showing.  This is determined by checking
553          * the visibility of the object and its ancestors.
554          * Note: this
555          * will return true even if the object is obscured by another (for
556          * example, it is underneath a menu that was pulled down).
557          *
558          * @return true if object is showing; otherwise, false
559          */
560         public boolean isShowing() {
561             return getTextComponent().isShowing();
562         }
563 
564         /**
565          * Checks whether the specified point is within this object's bounds,
566          * where the point's x and y coordinates are defined to be relative
567          * to the coordinate system of the object.
568          *
569          * @param p the Point relative to the coordinate system of the object
570          * @return true if object contains Point; otherwise false
571          * @see #getBounds
572          */
573         public boolean contains(Point p) {
574             Rectangle r = getBounds();
575             if (r != null) {
576                 return r.contains(p.x, p.y);
577             } else {
578                 return false;
579             }
580         }
581 
582         /**
583          * Returns the location of the object on the screen.
584          *
585          * @return the location of the object on screen; null if this object
586          * is not on the screen
587          * @see #getBounds
588          * @see #getLocation
589          */
590         public Point getLocationOnScreen() {
591             Point editorLocation = getTextComponent().getLocationOnScreen();
592             Rectangle r = getBounds();
593             if (r != null) {
594                 return new Point(editorLocation.x + r.x,
595                                  editorLocation.y + r.y);
596             } else {
597                 return null;
598             }
599         }
600 
601         /**
602          * Gets the location of the object relative to the parent in the form
603          * of a point specifying the object's top-left corner in the screen's
604          * coordinate space.
605          *
606          * @return An instance of Point representing the top-left corner of the
607          * object's bounds in the coordinate space of the screen; null if
608          * this object or its parent are not on the screen
609          * @see #getBounds
610          * @see #getLocationOnScreen
611          */
612         public Point getLocation() {
613             Rectangle r = getBounds();
614             if (r != null) {
615                 return new Point(r.x, r.y);
616             } else {
617                 return null;
618             }
619         }
620 
621         /**
622          * Sets the location of the object relative to the parent.
623          * @param p the new position for the top-left corner
624          * @see #getLocation
625          */
626         public void setLocation(Point p) {
627         }
628 
629         /**
630          * Gets the bounds of this object in the form of a Rectangle object.
631          * The bounds specify this object's width, height, and location
632          * relative to its parent.
633          *
634          * @return A rectangle indicating this component's bounds; null if
635          * this object is not on the screen.
636          * @see #contains
637          */
638         public Rectangle getBounds() {
639             return elementInfo.getBounds();
640         }
641 
642         /**
643          * Sets the bounds of this object in the form of a Rectangle object.
644          * The bounds specify this object's width, height, and location
645          * relative to its parent.
646          *
647          * @param r rectangle indicating this component's bounds
648          * @see #getBounds
649          */
650         public void setBounds(Rectangle r) {
651         }
652 
653         /**
654          * Returns the size of this object in the form of a Dimension object.
655          * The height field of the Dimension object contains this object's
656          * height, and the width field of the Dimension object contains this
657          * object's width.
658          *
659          * @return A Dimension object that indicates the size of this component;
660          * null if this object is not on the screen
661          * @see #setSize
662          */
663         public Dimension getSize() {
664             Rectangle r = getBounds();
665             if (r != null) {
666                 return new Dimension(r.width, r.height);
667             } else {
668                 return null;
669             }
670         }
671 
672         /**
673          * Resizes this object so that it has width and height.
674          *
675          * @param d The dimension specifying the new size of the object.
676          * @see #getSize
677          */
678         public void setSize(Dimension d) {
679             Component comp = getTextComponent();
680             comp.setSize(d);
681         }
682 
683         /**
684          * Returns the Accessible child, if one exists, contained at the local
685          * coordinate Point.
686          *
687          * @param p The point relative to the coordinate system of this object.
688          * @return the Accessible, if it exists, at the specified location;
689          * otherwise null
690          */
691         public Accessible getAccessibleAt(Point p) {
692             ElementInfo innerMostElement = getElementInfoAt(rootElementInfo, p);
693             if (innerMostElement instanceof Accessible) {
694                 return (Accessible)innerMostElement;
695             } else {
696                 return null;
697             }
698         }
699 
700         private ElementInfo getElementInfoAt(ElementInfo elementInfo, Point p) {
701             if (elementInfo.getBounds() == null) {
702                 return null;
703             }
704             if (elementInfo.getChildCount() == 0 &&
705                 elementInfo.getBounds().contains(p)) {
706                 return elementInfo;
707 
708             } else {
709                 if (elementInfo instanceof TableElementInfo) {
710                     // Handle table caption as a special case since it's the
711                     // only table child that is not a table row.
712                     ElementInfo captionInfo =
713                         ((TableElementInfo)elementInfo).getCaptionInfo();
714                     if (captionInfo != null) {
715                         Rectangle bounds = captionInfo.getBounds();
716                         if (bounds != null && bounds.contains(p)) {
717                             return captionInfo;
718                         }
719                     }
720                 }
721                 for (int i = 0; i < elementInfo.getChildCount(); i++)
722 {
723                     ElementInfo childInfo = elementInfo.getChild(i);
724                     ElementInfo retValue = getElementInfoAt(childInfo, p);
725                     if (retValue != null) {
726                         return retValue;
727                     }
728                 }
729             }
730             return null;
731         }
732 
733         /**
734          * Returns whether this object can accept focus or not.   Objects that
735          * can accept focus will also have the AccessibleState.FOCUSABLE state
736          * set in their AccessibleStateSets.
737          *
738          * @return true if object can accept focus; otherwise false
739          * @see AccessibleContext#getAccessibleStateSet
740          * @see AccessibleState#FOCUSABLE
741          * @see AccessibleState#FOCUSED
742          * @see AccessibleStateSet
743          */
744         public boolean isFocusTraversable() {
745             Component comp = getTextComponent();
746             if (comp instanceof JTextComponent) {
747                 if (((JTextComponent)comp).isEditable()) {
748                     return true;
749                 }
750             }
751             return false;
752         }
753 
754         /**
755          * Requests focus for this object.  If this object cannot accept focus,
756          * nothing will happen.  Otherwise, the object will attempt to take
757          * focus.
758          * @see #isFocusTraversable
759          */
760         public void requestFocus() {
761             // TIGER - 4856191
762             if (! isFocusTraversable()) {
763                 return;
764             }
765 
766             Component comp = getTextComponent();
767             if (comp instanceof JTextComponent) {
768 
769                 comp.requestFocusInWindow();
770 
771                 try {
772                     if (elementInfo.validateIfNecessary()) {
773                         // set the caret position to the start of this component
774                         Element elem = elementInfo.getElement();
775                         ((JTextComponent)comp).setCaretPosition(elem.getStartOffset());
776 
777                         // fire a AccessibleState.FOCUSED property change event
778                         AccessibleContext ac = editor.getAccessibleContext();
779                         PropertyChangeEvent pce = new PropertyChangeEvent(this,
780                             AccessibleContext.ACCESSIBLE_STATE_PROPERTY,
781                             null, AccessibleState.FOCUSED);
782                         ac.firePropertyChange(
783                             AccessibleContext.ACCESSIBLE_STATE_PROPERTY,
784                             null, pce);
785                     }
786                 } catch (IllegalArgumentException e) {
787                     // don't fire property change event
788                 }
789             }
790         }
791 
792         /**
793          * Adds the specified focus listener to receive focus events from this
794          * component.
795          *
796          * @param l the focus listener
797          * @see #removeFocusListener
798          */
799         public void addFocusListener(FocusListener l) {
800             getTextComponent().addFocusListener(l);
801         }
802 
803         /**
804          * Removes the specified focus listener so it no longer receives focus
805          * events from this component.
806          *
807          * @param l the focus listener
808          * @see #addFocusListener
809          */
810         public void removeFocusListener(FocusListener l) {
811             getTextComponent().removeFocusListener(l);
812         }
813         // ... end AccessibleComponent implementation
814     } // ... end HTMLAccessibleContext
815 
816 
817 
818     /*
819      * ElementInfo for text
820      */
821     class TextElementInfo extends ElementInfo implements Accessible {
822 
823         TextElementInfo(Element element, ElementInfo parent) {
824             super(element, parent);
825         }
826 
827         // begin AccessibleText implementation ...
828         private AccessibleContext accessibleContext;
829 
830         public AccessibleContext getAccessibleContext() {
831             if (accessibleContext == null) {
832                 accessibleContext = new TextAccessibleContext(this);
833             }
834             return accessibleContext;
835         }
836 
837         /*
838          * AccessibleContext for text elements
839          */
840         public class TextAccessibleContext extends HTMLAccessibleContext
841             implements AccessibleText {
842 
843             public TextAccessibleContext(ElementInfo elementInfo) {
844                 super(elementInfo);
845             }
846 
847             public AccessibleText getAccessibleText() {
848                 return this;
849             }
850 
851             /**
852              * Gets the accessibleName property of this object.  The accessibleName
853              * property of an object is a localized String that designates the purpose
854              * of the object.  For example, the accessibleName property of a label
855              * or button might be the text of the label or button itself.  In the
856              * case of an object that doesn't display its name, the accessibleName
857              * should still be set.  For example, in the case of a text field used
858              * to enter the name of a city, the accessibleName for the en_US locale
859              * could be 'city.'
860              *
861              * @return the localized name of the object; null if this
862              * object does not have a name
863              *
864              * @see #setAccessibleName
865              */
866             public String getAccessibleName() {
867                 if (model != null) {
868                     return (String)model.getProperty(Document.TitleProperty);
869                 } else {
870                     return null;
871                 }
872             }
873 
874             /**
875              * Gets the accessibleDescription property of this object.  If this
876              * property isn't set, returns the content type of this
877              * <code>JEditorPane</code> instead (e.g. "plain/text", "html/text").
878              *
879              * @return the localized description of the object; <code>null</code>
880              *  if this object does not have a description
881              *
882              * @see #setAccessibleName
883              */
884             public String getAccessibleDescription() {
885                 return editor.getContentType();
886             }
887 
888             /**
889              * Gets the role of this object.  The role of the object is the generic
890              * purpose or use of the class of this object.  For example, the role
891              * of a push button is AccessibleRole.PUSH_BUTTON.  The roles in
892              * AccessibleRole are provided so component developers can pick from
893              * a set of predefined roles.  This enables assistive technologies to
894              * provide a consistent interface to various tweaked subclasses of
895              * components (e.g., use AccessibleRole.PUSH_BUTTON for all components
896              * that act like a push button) as well as distinguish between sublasses
897              * that behave differently (e.g., AccessibleRole.CHECK_BOX for check boxes
898              * and AccessibleRole.RADIO_BUTTON for radio buttons).
899              * <p>Note that the AccessibleRole class is also extensible, so
900              * custom component developers can define their own AccessibleRole's
901              * if the set of predefined roles is inadequate.
902              *
903              * @return an instance of AccessibleRole describing the role of the object
904              * @see AccessibleRole
905              */
906             public AccessibleRole getAccessibleRole() {
907                 return AccessibleRole.TEXT;
908             }
909 
910             /**
911              * Given a point in local coordinates, return the zero-based index
912              * of the character under that Point.  If the point is invalid,
913              * this method returns -1.
914              *
915              * @param p the Point in local coordinates
916              * @return the zero-based index of the character under Point p; if
917              * Point is invalid returns -1.
918              */
919             public int getIndexAtPoint(Point p) {
920                 View v = getView();
921                 if (v != null) {
922                     return v.viewToModel(p.x, p.y, getBounds());
923                 } else {
924                     return -1;
925                 }
926             }
927 
928             /**
929              * Determine the bounding box of the character at the given
930              * index into the string.  The bounds are returned in local
931              * coordinates.  If the index is invalid an empty rectangle is
932              * returned.
933              *
934              * @param i the index into the String
935              * @return the screen coordinates of the character's the bounding box,
936              * if index is invalid returns an empty rectangle.
937              */
938             public Rectangle getCharacterBounds(int i) {
939                 try {
940                     return editor.getUI().modelToView(editor, i);
941                 } catch (BadLocationException e) {
942                     return null;
943                 }
944             }
945 
946             /**
947              * Return the number of characters (valid indicies)
948              *
949              * @return the number of characters
950              */
951             public int getCharCount() {
952                 if (validateIfNecessary()) {
953                     Element elem = elementInfo.getElement();
954                     return elem.getEndOffset() - elem.getStartOffset();
955                 }
956                 return 0;
957             }
958 
959             /**
960              * Return the zero-based offset of the caret.
961              *
962              * Note: That to the right of the caret will have the same index
963              * value as the offset (the caret is between two characters).
964              * @return the zero-based offset of the caret.
965              */
966             public int getCaretPosition() {
967                 View v = getView();
968                 if (v == null) {
969                     return -1;
970                 }
971                 Container c = v.getContainer();
972                 if (c == null) {
973                     return -1;
974                 }
975                 if (c instanceof JTextComponent) {
976                     return ((JTextComponent)c).getCaretPosition();
977                 } else {
978                     return -1;
979                 }
980             }
981 
982             /**
983              * IndexedSegment extends Segment adding the offset into the
984              * the model the <code>Segment</code> was asked for.
985              */
986             private class IndexedSegment extends Segment {
987                 /**
988                  * Offset into the model that the position represents.
989                  */
990                 public int modelOffset;
991             }
992 
993             public String getAtIndex(int part, int index) {
994                 return getAtIndex(part, index, 0);
995             }
996 
997 
998             public String getAfterIndex(int part, int index) {
999                 return getAtIndex(part, index, 1);
1000             }
1001 
1002             public String getBeforeIndex(int part, int index) {
1003                 return getAtIndex(part, index, -1);
1004             }
1005 
1006             /**
1007              * Gets the word, sentence, or character at <code>index</code>.
1008              * If <code>direction</code> is non-null this will find the
1009              * next/previous word/sentence/character.
1010              */
1011             private String getAtIndex(int part, int index, int direction) {
1012                 if (model instanceof AbstractDocument) {
1013                     ((AbstractDocument)model).readLock();
1014                 }
1015                 try {
1016                     if (index < 0 || index >= model.getLength()) {
1017                         return null;
1018                     }
1019                     switch (part) {
1020                     case AccessibleText.CHARACTER:
1021                         if (index + direction < model.getLength() &&
1022                             index + direction >= 0) {
1023                             return model.getText(index + direction, 1);
1024                         }
1025                         break;
1026 
1027 
1028                     case AccessibleText.WORD:
1029                     case AccessibleText.SENTENCE:
1030                         IndexedSegment seg = getSegmentAt(part, index);
1031                         if (seg != null) {
1032                             if (direction != 0) {
1033                                 int next;
1034 
1035 
1036                                 if (direction < 0) {
1037                                     next = seg.modelOffset - 1;
1038                                 }
1039                                 else {
1040                                     next = seg.modelOffset + direction * seg.count;
1041                                 }
1042                                 if (next >= 0 && next <= model.getLength()) {
1043                                     seg = getSegmentAt(part, next);
1044                                 }
1045                                 else {
1046                                     seg = null;
1047                                 }
1048                             }
1049                             if (seg != null) {
1050                                 return new String(seg.array, seg.offset,
1051                                                   seg.count);
1052                             }
1053                         }
1054                         break;
1055 
1056                     default:
1057                         break;
1058                     }
1059                 } catch (BadLocationException e) {
1060                 } finally {
1061                     if (model instanceof AbstractDocument) {
1062                         ((AbstractDocument)model).readUnlock();
1063                     }
1064                 }
1065                 return null;
1066             }
1067 
1068             /*
1069              * Returns the paragraph element for the specified index.
1070              */
1071             private Element getParagraphElement(int index) {
1072                 if (model instanceof PlainDocument ) {
1073                     PlainDocument sdoc = (PlainDocument)model;
1074                     return sdoc.getParagraphElement(index);
1075                 } else if (model instanceof StyledDocument) {
1076                     StyledDocument sdoc = (StyledDocument)model;
1077                     return sdoc.getParagraphElement(index);
1078                 } else {
1079                     Element para;
1080                     for (para = model.getDefaultRootElement(); ! para.isLeaf(); ) {
1081                         int pos = para.getElementIndex(index);
1082                         para = para.getElement(pos);
1083                     }
1084                     if (para == null) {
1085                         return null;
1086                     }
1087                     return para.getParentElement();
1088                 }
1089             }
1090 
1091             /*
1092              * Returns a <code>Segment</code> containing the paragraph text
1093              * at <code>index</code>, or null if <code>index</code> isn't
1094              * valid.
1095              */
1096             private IndexedSegment getParagraphElementText(int index)
1097                 throws BadLocationException {
1098                 Element para = getParagraphElement(index);
1099 
1100 
1101                 if (para != null) {
1102                     IndexedSegment segment = new IndexedSegment();
1103                     try {
1104                         int length = para.getEndOffset() - para.getStartOffset();
1105                         model.getText(para.getStartOffset(), length, segment);
1106                     } catch (BadLocationException e) {
1107                         return null;
1108                     }
1109                     segment.modelOffset = para.getStartOffset();
1110                     return segment;
1111                 }
1112                 return null;
1113             }
1114 
1115 
1116             /**
1117              * Returns the Segment at <code>index</code> representing either
1118              * the paragraph or sentence as identified by <code>part</code>, or
1119              * null if a valid paragraph/sentence can't be found. The offset
1120              * will point to the start of the word/sentence in the array, and
1121              * the modelOffset will point to the location of the word/sentence
1122              * in the model.
1123              */
1124             private IndexedSegment getSegmentAt(int part, int index)
1125                 throws BadLocationException {
1126 
1127                 IndexedSegment seg = getParagraphElementText(index);
1128                 if (seg == null) {
1129                     return null;
1130                 }
1131                 BreakIterator iterator;
1132                 switch (part) {
1133                 case AccessibleText.WORD:
1134                     iterator = BreakIterator.getWordInstance(getLocale());
1135                     break;
1136                 case AccessibleText.SENTENCE:
1137                     iterator = BreakIterator.getSentenceInstance(getLocale());
1138                     break;
1139                 default:
1140                     return null;
1141                 }
1142                 seg.first();
1143                 iterator.setText(seg);
1144                 int end = iterator.following(index - seg.modelOffset + seg.offset);
1145                 if (end == BreakIterator.DONE) {
1146                     return null;
1147                 }
1148                 if (end > seg.offset + seg.count) {
1149                     return null;
1150                 }
1151                 int begin = iterator.previous();
1152                 if (begin == BreakIterator.DONE ||
1153                     begin >= seg.offset + seg.count) {
1154                     return null;
1155                 }
1156                 seg.modelOffset = seg.modelOffset + begin - seg.offset;
1157                 seg.offset = begin;
1158                 seg.count = end - begin;
1159                 return seg;
1160             }
1161 
1162             /**
1163              * Return the AttributeSet for a given character at a given index
1164              *
1165              * @param i the zero-based index into the text
1166              * @return the AttributeSet of the character
1167              */
1168             public AttributeSet getCharacterAttribute(int i) {
1169                 if (model instanceof StyledDocument) {
1170                     StyledDocument doc = (StyledDocument)model;
1171                     Element elem = doc.getCharacterElement(i);
1172                     if (elem != null) {
1173                         return elem.getAttributes();
1174                     }
1175                 }
1176                 return null;
1177             }
1178 
1179             /**
1180              * Returns the start offset within the selected text.
1181              * If there is no selection, but there is
1182              * a caret, the start and end offsets will be the same.
1183              *
1184              * @return the index into the text of the start of the selection
1185              */
1186             public int getSelectionStart() {
1187                 return editor.getSelectionStart();
1188             }
1189 
1190             /**
1191              * Returns the end offset within the selected text.
1192              * If there is no selection, but there is
1193              * a caret, the start and end offsets will be the same.
1194              *
1195              * @return the index into teh text of the end of the selection
1196              */
1197             public int getSelectionEnd() {
1198                 return editor.getSelectionEnd();
1199             }
1200 
1201             /**
1202              * Returns the portion of the text that is selected.
1203              *
1204              * @return the String portion of the text that is selected
1205              */
1206             public String getSelectedText() {
1207                 return editor.getSelectedText();
1208             }
1209 
1210             /*
1211              * Returns the text substring starting at the specified
1212              * offset with the specified length.
1213              */
1214             private String getText(int offset, int length)
1215                 throws BadLocationException {
1216 
1217                 if (model != null && model instanceof StyledDocument) {
1218                     StyledDocument doc = (StyledDocument)model;
1219                     return model.getText(offset, length);
1220                 } else {
1221                     return null;
1222                 }
1223             }
1224         }
1225     }
1226 
1227     /*
1228      * ElementInfo for images
1229      */
1230     private class IconElementInfo extends ElementInfo implements Accessible {
1231 
1232         private int width = -1;
1233         private int height = -1;
1234 
1235         IconElementInfo(Element element, ElementInfo parent) {
1236             super(element, parent);
1237         }
1238 
1239         protected void invalidate(boolean first) {
1240             super.invalidate(first);
1241             width = height = -1;
1242         }
1243 
1244         private int getImageSize(Object key) {
1245             if (validateIfNecessary()) {
1246                 int size = getIntAttr(getAttributes(), key, -1);
1247 
1248                 if (size == -1) {
1249                     View v = getView();
1250 
1251                     size = 0;
1252                     if (v instanceof ImageView) {
1253                         Image img = ((ImageView)v).getImage();
1254                         if (img != null) {
1255                             if (key == HTML.Attribute.WIDTH) {
1256                                 size = img.getWidth(null);
1257                             }
1258                             else {
1259                                 size = img.getHeight(null);
1260                             }
1261                         }
1262                     }
1263                 }
1264                 return size;
1265             }
1266             return 0;
1267         }
1268 
1269         // begin AccessibleIcon implementation ...
1270         private AccessibleContext accessibleContext;
1271 
1272         public AccessibleContext getAccessibleContext() {
1273             if (accessibleContext == null) {
1274                 accessibleContext = new IconAccessibleContext(this);
1275             }
1276             return accessibleContext;
1277         }
1278 
1279         /*
1280          * AccessibleContext for images
1281          */
1282         protected class IconAccessibleContext extends HTMLAccessibleContext
1283             implements AccessibleIcon  {
1284 
1285             public IconAccessibleContext(ElementInfo elementInfo) {
1286                 super(elementInfo);
1287             }
1288 
1289             /**
1290              * Gets the accessibleName property of this object.  The accessibleName
1291              * property of an object is a localized String that designates the purpose
1292              * of the object.  For example, the accessibleName property of a label
1293              * or button might be the text of the label or button itself.  In the
1294              * case of an object that doesn't display its name, the accessibleName
1295              * should still be set.  For example, in the case of a text field used
1296              * to enter the name of a city, the accessibleName for the en_US locale
1297              * could be 'city.'
1298              *
1299              * @return the localized name of the object; null if this
1300              * object does not have a name
1301              *
1302              * @see #setAccessibleName
1303              */
1304             public String getAccessibleName() {
1305                 return getAccessibleIconDescription();
1306             }
1307 
1308             /**
1309              * Gets the accessibleDescription property of this object.  If this
1310              * property isn't set, returns the content type of this
1311              * <code>JEditorPane</code> instead (e.g. "plain/text", "html/text").
1312              *
1313              * @return the localized description of the object; <code>null</code>
1314              *  if this object does not have a description
1315              *
1316              * @see #setAccessibleName
1317              */
1318             public String getAccessibleDescription() {
1319                 return editor.getContentType();
1320             }
1321 
1322             /**
1323              * Gets the role of this object.  The role of the object is the generic
1324              * purpose or use of the class of this object.  For example, the role
1325              * of a push button is AccessibleRole.PUSH_BUTTON.  The roles in
1326              * AccessibleRole are provided so component developers can pick from
1327              * a set of predefined roles.  This enables assistive technologies to
1328              * provide a consistent interface to various tweaked subclasses of
1329              * components (e.g., use AccessibleRole.PUSH_BUTTON for all components
1330              * that act like a push button) as well as distinguish between sublasses
1331              * that behave differently (e.g., AccessibleRole.CHECK_BOX for check boxes
1332              * and AccessibleRole.RADIO_BUTTON for radio buttons).
1333              * <p>Note that the AccessibleRole class is also extensible, so
1334              * custom component developers can define their own AccessibleRole's
1335              * if the set of predefined roles is inadequate.
1336              *
1337              * @return an instance of AccessibleRole describing the role of the object
1338              * @see AccessibleRole
1339              */
1340             public AccessibleRole getAccessibleRole() {
1341                 return AccessibleRole.ICON;
1342             }
1343 
1344             public AccessibleIcon [] getAccessibleIcon() {
1345                 AccessibleIcon [] icons = new AccessibleIcon[1];
1346                 icons[0] = this;
1347                 return icons;
1348             }
1349 
1350             /**
1351              * Gets the description of the icon.  This is meant to be a brief
1352              * textual description of the object.  For example, it might be
1353              * presented to a blind user to give an indication of the purpose
1354              * of the icon.
1355              *
1356              * @return the description of the icon
1357              */
1358             public String getAccessibleIconDescription() {
1359                 return ((ImageView)getView()).getAltText();
1360             }
1361 
1362             /**
1363              * Sets the description of the icon.  This is meant to be a brief
1364              * textual description of the object.  For example, it might be
1365              * presented to a blind user to give an indication of the purpose
1366              * of the icon.
1367              *
1368              * @param description the description of the icon
1369              */
1370             public void setAccessibleIconDescription(String description) {
1371             }
1372 
1373             /**
1374              * Gets the width of the icon
1375              *
1376              * @return the width of the icon.
1377              */
1378             public int getAccessibleIconWidth() {
1379                 if (width == -1) {
1380                     width = getImageSize(HTML.Attribute.WIDTH);
1381                 }
1382                 return width;
1383             }
1384 
1385             /**
1386              * Gets the height of the icon
1387              *
1388              * @return the height of the icon.
1389              */
1390             public int getAccessibleIconHeight() {
1391                 if (height == -1) {
1392                     height = getImageSize(HTML.Attribute.HEIGHT);
1393                 }
1394                 return height;
1395             }
1396         }
1397         // ... end AccessibleIconImplementation
1398     }
1399 
1400 
1401     /**
1402      * TableElementInfo encapsulates information about a HTML.Tag.TABLE.
1403      * To make access fast it crates a grid containing the children to
1404      * allow for access by row, column. TableElementInfo will contain
1405      * TableRowElementInfos, which will contain TableCellElementInfos.
1406      * Any time one of the rows or columns becomes invalid the table is
1407      * invalidated.  This is because any time one of the child attributes
1408      * changes the size of the grid may have changed.
1409      */
1410     private class TableElementInfo extends ElementInfo
1411         implements Accessible {
1412 
1413         protected ElementInfo caption;
1414 
1415         /**
1416          * Allocation of the table by row x column. There may be holes (eg
1417          * nulls) depending upon the html, any cell that has a rowspan/colspan
1418          * > 1 will be contained multiple times in the grid.
1419          */
1420         private TableCellElementInfo[][] grid;
1421 
1422 
1423         TableElementInfo(Element e, ElementInfo parent) {
1424             super(e, parent);
1425         }
1426 
1427         public ElementInfo getCaptionInfo() {
1428             return caption;
1429         }
1430 
1431         /**
1432          * Overriden to update the grid when validating.
1433          */
1434         protected void validate() {
1435             super.validate();
1436             updateGrid();
1437         }
1438 
1439         /**
1440          * Overriden to only alloc instances of TableRowElementInfos.
1441          */
1442         protected void loadChildren(Element e) {
1443 
1444             for (int counter = 0; counter < e.getElementCount(); counter++) {
1445                 Element child = e.getElement(counter);
1446                 AttributeSet attrs = child.getAttributes();
1447 
1448                 if (attrs.getAttribute(StyleConstants.NameAttribute) ==
1449                                        HTML.Tag.TR) {
1450                     addChild(new TableRowElementInfo(child, this, counter));
1451 
1452                 } else if (attrs.getAttribute(StyleConstants.NameAttribute) ==
1453                                        HTML.Tag.CAPTION) {
1454                     // Handle captions as a special case since all other
1455                     // children are table rows.
1456                     caption = createElementInfo(child, this);
1457                 }
1458             }
1459         }
1460 
1461         /**
1462          * Updates the grid.
1463          */
1464         private void updateGrid() {
1465             // Determine the max row/col count.
1466             int delta = 0;
1467             int maxCols = 0;
1468             int rows;
1469             for (int counter = 0; counter < getChildCount(); counter++) {
1470                 TableRowElementInfo row = getRow(counter);
1471                 int prev = 0;
1472                 for (int y = 0; y < delta; y++) {
1473                     prev = Math.max(prev, getRow(counter - y - 1).
1474                                     getColumnCount(y + 2));
1475                 }
1476                 delta = Math.max(row.getRowCount(), delta);
1477                 delta--;
1478                 maxCols = Math.max(maxCols, row.getColumnCount() + prev);
1479             }
1480             rows = getChildCount() + delta;
1481 
1482             // Alloc
1483             grid = new TableCellElementInfo[rows][];
1484             for (int counter = 0; counter < rows; counter++) {
1485                 grid[counter] = new TableCellElementInfo[maxCols];
1486             }
1487             // Update
1488             for (int counter = 0; counter < rows; counter++) {
1489                 getRow(counter).updateGrid(counter);
1490             }
1491         }
1492 
1493         /**
1494          * Returns the TableCellElementInfo at the specified index.
1495          */
1496         public TableRowElementInfo getRow(int index) {
1497             return (TableRowElementInfo)getChild(index);
1498         }
1499 
1500         /**
1501          * Returns the TableCellElementInfo by row and column.
1502          */
1503         public TableCellElementInfo getCell(int r, int c) {
1504             if (validateIfNecessary() && r < grid.length &&
1505                                          c < grid[0].length) {
1506                 return grid[r][c];
1507             }
1508             return null;
1509         }
1510 
1511         /**
1512          * Returns the rowspan of the specified entry.
1513          */
1514         public int getRowExtentAt(int r, int c) {
1515             TableCellElementInfo cell = getCell(r, c);
1516 
1517             if (cell != null) {
1518                 int rows = cell.getRowCount();
1519                 int delta = 1;
1520 
1521                 while ((r - delta) >= 0 && grid[r - delta][c] == cell) {
1522                     delta++;
1523                 }
1524                 return rows - delta + 1;
1525             }
1526             return 0;
1527         }
1528 
1529         /**
1530          * Returns the colspan of the specified entry.
1531          */
1532         public int getColumnExtentAt(int r, int c) {
1533             TableCellElementInfo cell = getCell(r, c);
1534 
1535             if (cell != null) {
1536                 int cols = cell.getColumnCount();
1537                 int delta = 1;
1538 
1539                 while ((c - delta) >= 0 && grid[r][c - delta] == cell) {
1540                     delta++;
1541                 }
1542                 return cols - delta + 1;
1543             }
1544             return 0;
1545         }
1546 
1547         /**
1548          * Returns the number of rows in the table.
1549          */
1550         public int getRowCount() {
1551             if (validateIfNecessary()) {
1552                 return grid.length;
1553             }
1554             return 0;
1555         }
1556 
1557         /**
1558          * Returns the number of columns in the table.
1559          */
1560         public int getColumnCount() {
1561             if (validateIfNecessary() && grid.length > 0) {
1562                 return grid[0].length;
1563             }
1564             return 0;
1565         }
1566 
1567         // begin AccessibleTable implementation ...
1568         private AccessibleContext accessibleContext;
1569 
1570         public AccessibleContext getAccessibleContext() {
1571             if (accessibleContext == null) {
1572                 accessibleContext = new TableAccessibleContext(this);
1573             }
1574             return accessibleContext;
1575         }
1576 
1577         /*
1578          * AccessibleContext for tables
1579          */
1580         public class TableAccessibleContext extends HTMLAccessibleContext
1581             implements AccessibleTable {
1582 
1583             private AccessibleHeadersTable rowHeadersTable;
1584 
1585             public TableAccessibleContext(ElementInfo elementInfo) {
1586                 super(elementInfo);
1587             }
1588 
1589             /**
1590              * Gets the accessibleName property of this object.  The accessibleName
1591              * property of an object is a localized String that designates the purpose
1592              * of the object.  For example, the accessibleName property of a label
1593              * or button might be the text of the label or button itself.  In the
1594              * case of an object that doesn't display its name, the accessibleName
1595              * should still be set.  For example, in the case of a text field used
1596              * to enter the name of a city, the accessibleName for the en_US locale
1597              * could be 'city.'
1598              *
1599              * @return the localized name of the object; null if this
1600              * object does not have a name
1601              *
1602              * @see #setAccessibleName
1603              */
1604             public String getAccessibleName() {
1605                 // return the role of the object
1606                 return getAccessibleRole().toString();
1607             }
1608 
1609             /**
1610              * Gets the accessibleDescription property of this object.  If this
1611              * property isn't set, returns the content type of this
1612              * <code>JEditorPane</code> instead (e.g. "plain/text", "html/text").
1613              *
1614              * @return the localized description of the object; <code>null</code>
1615              *  if this object does not have a description
1616              *
1617              * @see #setAccessibleName
1618              */
1619             public String getAccessibleDescription() {
1620                 return editor.getContentType();
1621             }
1622 
1623             /**
1624              * Gets the role of this object.  The role of the object is the generic
1625              * purpose or use of the class of this object.  For example, the role
1626              * of a push button is AccessibleRole.PUSH_BUTTON.  The roles in
1627              * AccessibleRole are provided so component developers can pick from
1628              * a set of predefined roles.  This enables assistive technologies to
1629              * provide a consistent interface to various tweaked subclasses of
1630              * components (e.g., use AccessibleRole.PUSH_BUTTON for all components
1631              * that act like a push button) as well as distinguish between sublasses
1632              * that behave differently (e.g., AccessibleRole.CHECK_BOX for check boxes
1633              * and AccessibleRole.RADIO_BUTTON for radio buttons).
1634              * <p>Note that the AccessibleRole class is also extensible, so
1635              * custom component developers can define their own AccessibleRole's
1636              * if the set of predefined roles is inadequate.
1637              *
1638              * @return an instance of AccessibleRole describing the role of the object
1639              * @see AccessibleRole
1640              */
1641             public AccessibleRole getAccessibleRole() {
1642                 return AccessibleRole.TABLE;
1643             }
1644 
1645             /**
1646              * Gets the 0-based index of this object in its accessible parent.
1647              *
1648              * @return the 0-based index of this object in its parent; -1 if this
1649              * object does not have an accessible parent.
1650              *
1651              * @see #getAccessibleParent
1652              * @see #getAccessibleChildrenCount
1653              * @gsee #getAccessibleChild
1654              */
1655             public int getAccessibleIndexInParent() {
1656                 return elementInfo.getIndexInParent();
1657             }
1658 
1659             /**
1660              * Returns the number of accessible children of the object.
1661              *
1662              * @return the number of accessible children of the object.
1663              */
1664             public int getAccessibleChildrenCount() {
1665                 return ((TableElementInfo)elementInfo).getRowCount() *
1666                     ((TableElementInfo)elementInfo).getColumnCount();
1667             }
1668 
1669             /**
1670              * Returns the specified Accessible child of the object.  The Accessible
1671              * children of an Accessible object are zero-based, so the first child
1672              * of an Accessible child is at index 0, the second child is at index 1,
1673              * and so on.
1674              *
1675              * @param i zero-based index of child
1676              * @return the Accessible child of the object
1677              * @see #getAccessibleChildrenCount
1678              */
1679             public Accessible getAccessibleChild(int i) {
1680                 int rowCount = ((TableElementInfo)elementInfo).getRowCount();
1681                 int columnCount = ((TableElementInfo)elementInfo).getColumnCount();
1682                 int r = i / rowCount;
1683                 int c = i % columnCount;
1684                 if (r < 0 || r >= rowCount || c < 0 || c >= columnCount) {
1685                     return null;
1686                 } else {
1687                     return getAccessibleAt(r, c);
1688                 }
1689             }
1690 
1691             public AccessibleTable getAccessibleTable() {
1692                 return this;
1693             }
1694 
1695             /**
1696              * Returns the caption for the table.
1697              *
1698              * @return the caption for the table
1699              */
1700             public Accessible getAccessibleCaption() {
1701                 ElementInfo captionInfo = getCaptionInfo();
1702                 if (captionInfo instanceof Accessible) {
1703                     return (Accessible)caption;
1704                 } else {
1705                     return null;
1706                 }
1707             }
1708 
1709             /**
1710              * Sets the caption for the table.
1711              *
1712              * @param a the caption for the table
1713              */
1714             public void setAccessibleCaption(Accessible a) {
1715             }
1716 
1717             /**
1718              * Returns the summary description of the table.
1719              *
1720              * @return the summary description of the table
1721              */
1722             public Accessible getAccessibleSummary() {
1723                 return null;
1724             }
1725 
1726             /**
1727              * Sets the summary description of the table
1728              *
1729              * @param a the summary description of the table
1730              */
1731             public void setAccessibleSummary(Accessible a) {
1732             }
1733 
1734             /**
1735              * Returns the number of rows in the table.
1736              *
1737              * @return the number of rows in the table
1738              */
1739             public int getAccessibleRowCount() {
1740                 return ((TableElementInfo)elementInfo).getRowCount();
1741             }
1742 
1743             /**
1744              * Returns the number of columns in the table.
1745              *
1746              * @return the number of columns in the table
1747              */
1748             public int getAccessibleColumnCount() {
1749                 return ((TableElementInfo)elementInfo).getColumnCount();
1750             }
1751 
1752             /**
1753              * Returns the Accessible at a specified row and column
1754              * in the table.
1755              *
1756              * @param r zero-based row of the table
1757              * @param c zero-based column of the table
1758              * @return the Accessible at the specified row and column
1759              */
1760             public Accessible getAccessibleAt(int r, int c) {
1761                 TableCellElementInfo cellInfo = getCell(r, c);
1762                 if (cellInfo != null) {
1763                     return cellInfo.getAccessible();
1764                 } else {
1765                     return null;
1766                 }
1767             }
1768 
1769             /**
1770              * Returns the number of rows occupied by the Accessible at
1771              * a specified row and column in the table.
1772              *
1773              * @return the number of rows occupied by the Accessible at a
1774              * given specified (row, column)
1775              */
1776             public int getAccessibleRowExtentAt(int r, int c) {
1777                 return ((TableElementInfo)elementInfo).getRowExtentAt(r, c);
1778             }
1779 
1780             /**
1781              * Returns the number of columns occupied by the Accessible at
1782              * a specified row and column in the table.
1783              *
1784              * @return the number of columns occupied by the Accessible at a
1785              * given specified row and column
1786              */
1787             public int getAccessibleColumnExtentAt(int r, int c) {
1788                 return ((TableElementInfo)elementInfo).getColumnExtentAt(r, c);
1789             }
1790 
1791             /**
1792              * Returns the row headers as an AccessibleTable.
1793              *
1794              * @return an AccessibleTable representing the row
1795              * headers
1796              */
1797             public AccessibleTable getAccessibleRowHeader() {
1798                 return rowHeadersTable;
1799             }
1800 
1801             /**
1802              * Sets the row headers.
1803              *
1804              * @param table an AccessibleTable representing the
1805              * row headers
1806              */
1807             public void setAccessibleRowHeader(AccessibleTable table) {
1808             }
1809 
1810             /**
1811              * Returns the column headers as an AccessibleTable.
1812              *
1813              * @return an AccessibleTable representing the column
1814              * headers
1815              */
1816             public AccessibleTable getAccessibleColumnHeader() {
1817                 return null;
1818             }
1819 
1820             /**
1821              * Sets the column headers.
1822              *
1823              * @param table an AccessibleTable representing the
1824              * column headers
1825              */
1826             public void setAccessibleColumnHeader(AccessibleTable table) {
1827             }
1828 
1829             /**
1830              * Returns the description of the specified row in the table.
1831              *
1832              * @param r zero-based row of the table
1833              * @return the description of the row
1834              */
1835             public Accessible getAccessibleRowDescription(int r) {
1836                 return null;
1837             }
1838 
1839             /**
1840              * Sets the description text of the specified row of the table.
1841              *
1842              * @param r zero-based row of the table
1843              * @param a the description of the row
1844              */
1845             public void setAccessibleRowDescription(int r, Accessible a) {
1846             }
1847 
1848             /**
1849              * Returns the description text of the specified column in the table.
1850              *
1851              * @param c zero-based column of the table
1852              * @return the text description of the column
1853              */
1854             public Accessible getAccessibleColumnDescription(int c) {
1855                 return null;
1856             }
1857 
1858             /**
1859              * Sets the description text of the specified column in the table.
1860              *
1861              * @param c zero-based column of the table
1862              * @param a the text description of the column
1863              */
1864             public void setAccessibleColumnDescription(int c, Accessible a) {
1865             }
1866 
1867             /**
1868              * Returns a boolean value indicating whether the accessible at
1869              * a specified row and column is selected.
1870              *
1871              * @param r zero-based row of the table
1872              * @param c zero-based column of the table
1873              * @return the boolean value true if the accessible at the
1874              * row and column is selected. Otherwise, the boolean value
1875              * false
1876              */
1877             public boolean isAccessibleSelected(int r, int c) {
1878                 if (validateIfNecessary()) {
1879                     if (r < 0 || r >= getAccessibleRowCount() ||
1880                         c < 0 || c >= getAccessibleColumnCount()) {
1881                         return false;
1882                     }
1883                     TableCellElementInfo cell = getCell(r, c);
1884                     if (cell != null) {
1885                         Element elem = cell.getElement();
1886                         int start = elem.getStartOffset();
1887                         int end = elem.getEndOffset();
1888                         return start >= editor.getSelectionStart() &&
1889                             end <= editor.getSelectionEnd();
1890                     }
1891                 }
1892                 return false;
1893             }
1894 
1895             /**
1896              * Returns a boolean value indicating whether the specified row
1897              * is selected.
1898              *
1899              * @param r zero-based row of the table
1900              * @return the boolean value true if the specified row is selected.
1901              * Otherwise, false.
1902              */
1903             public boolean isAccessibleRowSelected(int r) {
1904                 if (validateIfNecessary()) {
1905                     if (r < 0 || r >= getAccessibleRowCount()) {
1906                         return false;
1907                     }
1908                     int nColumns = getAccessibleColumnCount();
1909 
1910                     TableCellElementInfo startCell = getCell(r, 0);
1911                     if (startCell == null) {
1912                         return false;
1913                     }
1914                     int start = startCell.getElement().getStartOffset();
1915 
1916                     TableCellElementInfo endCell = getCell(r, nColumns-1);
1917                     if (endCell == null) {
1918                         return false;
1919                     }
1920                     int end = endCell.getElement().getEndOffset();
1921 
1922                     return start >= editor.getSelectionStart() &&
1923                         end <= editor.getSelectionEnd();
1924                 }
1925                 return false;
1926             }
1927 
1928             /**
1929              * Returns a boolean value indicating whether the specified column
1930              * is selected.
1931              *
1932              * @param c zero-based column of the table
1933              * @return the boolean value true if the specified column is selected.
1934              * Otherwise, false.
1935              */
1936             public boolean isAccessibleColumnSelected(int c) {
1937                 if (validateIfNecessary()) {
1938                     if (c < 0 || c >= getAccessibleColumnCount()) {
1939                         return false;
1940                     }
1941                     int nRows = getAccessibleRowCount();
1942 
1943                     TableCellElementInfo startCell = getCell(0, c);
1944                     if (startCell == null) {
1945                         return false;
1946                     }
1947                     int start = startCell.getElement().getStartOffset();
1948 
1949                     TableCellElementInfo endCell = getCell(nRows-1, c);
1950                     if (endCell == null) {
1951                         return false;
1952                     }
1953                     int end = endCell.getElement().getEndOffset();
1954                     return start >= editor.getSelectionStart() &&
1955                         end <= editor.getSelectionEnd();
1956                 }
1957                 return false;
1958             }
1959 
1960             /**
1961              * Returns the selected rows in a table.
1962              *
1963              * @return an array of selected rows where each element is a
1964              * zero-based row of the table
1965              */
1966             public int [] getSelectedAccessibleRows() {
1967                 if (validateIfNecessary()) {
1968                     int nRows = getAccessibleRowCount();
1969                     Vector<Integer> vec = new Vector<Integer>();
1970 
1971                     for (int i = 0; i < nRows; i++) {
1972                         if (isAccessibleRowSelected(i)) {
1973                             vec.addElement(Integer.valueOf(i));
1974                         }
1975                     }
1976                     int retval[] = new int[vec.size()];
1977                     for (int i = 0; i < retval.length; i++) {
1978                         retval[i] = vec.elementAt(i).intValue();
1979                     }
1980                     return retval;
1981                 }
1982                 return new int[0];
1983             }
1984 
1985             /**
1986              * Returns the selected columns in a table.
1987              *
1988              * @return an array of selected columns where each element is a
1989              * zero-based column of the table
1990              */
1991             public int [] getSelectedAccessibleColumns() {
1992                 if (validateIfNecessary()) {
1993                     int nColumns = getAccessibleRowCount();
1994                     Vector<Integer> vec = new Vector<Integer>();
1995 
1996                     for (int i = 0; i < nColumns; i++) {
1997                         if (isAccessibleColumnSelected(i)) {
1998                             vec.addElement(Integer.valueOf(i));
1999                         }
2000                     }
2001                     int retval[] = new int[vec.size()];
2002                     for (int i = 0; i < retval.length; i++) {
2003                         retval[i] = vec.elementAt(i).intValue();
2004                     }
2005                     return retval;
2006                 }
2007                 return new int[0];
2008             }
2009 
2010             // begin AccessibleExtendedTable implementation -------------
2011 
2012             /**
2013              * Returns the row number of an index in the table.
2014              *
2015              * @param index the zero-based index in the table
2016              * @return the zero-based row of the table if one exists;
2017              * otherwise -1.
2018              */
2019             public int getAccessibleRow(int index) {
2020                 if (validateIfNecessary()) {
2021                     int numCells = getAccessibleColumnCount() *
2022                         getAccessibleRowCount();
2023                     if (index >= numCells) {
2024                         return -1;
2025                     } else {
2026                         return index / getAccessibleColumnCount();
2027                     }
2028                 }
2029                 return -1;
2030             }
2031 
2032             /**
2033              * Returns the column number of an index in the table.
2034              *
2035              * @param index the zero-based index in the table
2036              * @return the zero-based column of the table if one exists;
2037              * otherwise -1.
2038              */
2039             public int getAccessibleColumn(int index) {
2040                 if (validateIfNecessary()) {
2041                     int numCells = getAccessibleColumnCount() *
2042                         getAccessibleRowCount();
2043                     if (index >= numCells) {
2044                         return -1;
2045                     } else {
2046                         return index % getAccessibleColumnCount();
2047                     }
2048                 }
2049                 return -1;
2050             }
2051 
2052             /**
2053              * Returns the index at a row and column in the table.
2054              *
2055              * @param r zero-based row of the table
2056              * @param c zero-based column of the table
2057              * @return the zero-based index in the table if one exists;
2058              * otherwise -1.
2059              */
2060             public int getAccessibleIndex(int r, int c) {
2061                 if (validateIfNecessary()) {
2062                     if (r >= getAccessibleRowCount() ||
2063                         c >= getAccessibleColumnCount()) {
2064                         return -1;
2065                     } else {
2066                         return r * getAccessibleColumnCount() + c;
2067                     }
2068                 }
2069                 return -1;
2070             }
2071 
2072             /**
2073              * Returns the row header at a row in a table.
2074              * @param r zero-based row of the table
2075              *
2076              * @return a String representing the row header
2077              * if one exists; otherwise null.
2078              */
2079             public String getAccessibleRowHeader(int r) {
2080                 if (validateIfNecessary()) {
2081                     TableCellElementInfo cellInfo = getCell(r, 0);
2082                     if (cellInfo.isHeaderCell()) {
2083                         View v = cellInfo.getView();
2084                         if (v != null && model != null) {
2085                             try {
2086                                 return model.getText(v.getStartOffset(),
2087                                                      v.getEndOffset() -
2088                                                      v.getStartOffset());
2089                             } catch (BadLocationException e) {
2090                                 return null;
2091                             }
2092                         }
2093                     }
2094                 }
2095                 return null;
2096             }
2097 
2098             /**
2099              * Returns the column header at a column in a table.
2100              * @param c zero-based column of the table
2101              *
2102              * @return a String representing the column header
2103              * if one exists; otherwise null.
2104              */
2105             public String getAccessibleColumnHeader(int c) {
2106                 if (validateIfNecessary()) {
2107                     TableCellElementInfo cellInfo = getCell(0, c);
2108                     if (cellInfo.isHeaderCell()) {
2109                         View v = cellInfo.getView();
2110                         if (v != null && model != null) {
2111                             try {
2112                                 return model.getText(v.getStartOffset(),
2113                                                      v.getEndOffset() -
2114                                                      v.getStartOffset());
2115                             } catch (BadLocationException e) {
2116                                 return null;
2117                             }
2118                         }
2119                     }
2120                 }
2121                 return null;
2122             }
2123 
2124             public void addRowHeader(TableCellElementInfo cellInfo, int rowNumber) {
2125                 if (rowHeadersTable == null) {
2126                     rowHeadersTable = new AccessibleHeadersTable();
2127                 }
2128                 rowHeadersTable.addHeader(cellInfo, rowNumber);
2129             }
2130             // end of AccessibleExtendedTable implementation ------------
2131 
2132             protected class AccessibleHeadersTable implements AccessibleTable {
2133 
2134                 // Header information is modeled as a Hashtable of
2135                 // ArrayLists where each Hashtable entry represents
2136                 // a row containing one or more headers.
2137                 private Hashtable<Integer, ArrayList<TableCellElementInfo>> headers =
2138                         new Hashtable<Integer, ArrayList<TableCellElementInfo>>();
2139                 private int rowCount = 0;
2140                 private int columnCount = 0;
2141 
2142                 public void addHeader(TableCellElementInfo cellInfo, int rowNumber) {
2143                     Integer rowInteger = Integer.valueOf(rowNumber);
2144                     ArrayList<TableCellElementInfo> list = headers.get(rowInteger);
2145                     if (list == null) {
2146                         list = new ArrayList<TableCellElementInfo>();
2147                         headers.put(rowInteger, list);
2148                     }
2149                     list.add(cellInfo);
2150                 }
2151 
2152                 /**
2153                  * Returns the caption for the table.
2154                  *
2155                  * @return the caption for the table
2156                  */
2157                 public Accessible getAccessibleCaption() {
2158                     return null;
2159                 }
2160 
2161                 /**
2162                  * Sets the caption for the table.
2163                  *
2164                  * @param a the caption for the table
2165                  */
2166                 public void setAccessibleCaption(Accessible a) {
2167                 }
2168 
2169                 /**
2170                  * Returns the summary description of the table.
2171                  *
2172                  * @return the summary description of the table
2173                  */
2174                 public Accessible getAccessibleSummary() {
2175                     return null;
2176                 }
2177 
2178                 /**
2179                  * Sets the summary description of the table
2180                  *
2181                  * @param a the summary description of the table
2182                  */
2183                 public void setAccessibleSummary(Accessible a) {
2184                 }
2185 
2186                 /**
2187                  * Returns the number of rows in the table.
2188                  *
2189                  * @return the number of rows in the table
2190                  */
2191                 public int getAccessibleRowCount() {
2192                     return rowCount;
2193                 }
2194 
2195                 /**
2196                  * Returns the number of columns in the table.
2197                  *
2198                  * @return the number of columns in the table
2199                  */
2200                 public int getAccessibleColumnCount() {
2201                     return columnCount;
2202                 }
2203 
2204                 private TableCellElementInfo getElementInfoAt(int r, int c) {
2205                     ArrayList<TableCellElementInfo> list = headers.get(Integer.valueOf(r));
2206                     if (list != null) {
2207                         return list.get(c);
2208                     } else {
2209                         return null;
2210                     }
2211                 }
2212 
2213                 /**
2214                  * Returns the Accessible at a specified row and column
2215                  * in the table.
2216                  *
2217                  * @param r zero-based row of the table
2218                  * @param c zero-based column of the table
2219                  * @return the Accessible at the specified row and column
2220                  */
2221                 public Accessible getAccessibleAt(int r, int c) {
2222                     ElementInfo elementInfo = getElementInfoAt(r, c);
2223                     if (elementInfo instanceof Accessible) {
2224                         return (Accessible)elementInfo;
2225                     } else {
2226                         return null;
2227                     }
2228                 }
2229 
2230                 /**
2231                  * Returns the number of rows occupied by the Accessible at
2232                  * a specified row and column in the table.
2233                  *
2234                  * @return the number of rows occupied by the Accessible at a
2235                  * given specified (row, column)
2236                  */
2237                 public int getAccessibleRowExtentAt(int r, int c) {
2238                     TableCellElementInfo elementInfo = getElementInfoAt(r, c);
2239                     if (elementInfo != null) {
2240                         return elementInfo.getRowCount();
2241                     } else {
2242                         return 0;
2243                     }
2244                 }
2245 
2246                 /**
2247                  * Returns the number of columns occupied by the Accessible at
2248                  * a specified row and column in the table.
2249                  *
2250                  * @return the number of columns occupied by the Accessible at a
2251                  * given specified row and column
2252                  */
2253                 public int getAccessibleColumnExtentAt(int r, int c) {
2254                     TableCellElementInfo elementInfo = getElementInfoAt(r, c);
2255                     if (elementInfo != null) {
2256                         return elementInfo.getRowCount();
2257                     } else {
2258                         return 0;
2259                     }
2260                 }
2261 
2262                 /**
2263                  * Returns the row headers as an AccessibleTable.
2264                  *
2265                  * @return an AccessibleTable representing the row
2266                  * headers
2267                  */
2268                 public AccessibleTable getAccessibleRowHeader() {
2269                     return null;
2270                 }
2271 
2272                 /**
2273                  * Sets the row headers.
2274                  *
2275                  * @param table an AccessibleTable representing the
2276                  * row headers
2277                  */
2278                 public void setAccessibleRowHeader(AccessibleTable table) {
2279                 }
2280 
2281                 /**
2282                  * Returns the column headers as an AccessibleTable.
2283                  *
2284                  * @return an AccessibleTable representing the column
2285                  * headers
2286                  */
2287                 public AccessibleTable getAccessibleColumnHeader() {
2288                     return null;
2289                 }
2290 
2291                 /**
2292                  * Sets the column headers.
2293                  *
2294                  * @param table an AccessibleTable representing the
2295                  * column headers
2296                  */
2297                 public void setAccessibleColumnHeader(AccessibleTable table) {
2298                 }
2299 
2300                 /**
2301                  * Returns the description of the specified row in the table.
2302                  *
2303                  * @param r zero-based row of the table
2304                  * @return the description of the row
2305                  */
2306                 public Accessible getAccessibleRowDescription(int r) {
2307                     return null;
2308                 }
2309 
2310                 /**
2311                  * Sets the description text of the specified row of the table.
2312                  *
2313                  * @param r zero-based row of the table
2314                  * @param a the description of the row
2315                  */
2316                 public void setAccessibleRowDescription(int r, Accessible a) {
2317                 }
2318 
2319                 /**
2320                  * Returns the description text of the specified column in the table.
2321                  *
2322                  * @param c zero-based column of the table
2323                  * @return the text description of the column
2324                  */
2325                 public Accessible getAccessibleColumnDescription(int c) {
2326                     return null;
2327                 }
2328 
2329                 /**
2330                  * Sets the description text of the specified column in the table.
2331                  *
2332                  * @param c zero-based column of the table
2333                  * @param a the text description of the column
2334                  */
2335                 public void setAccessibleColumnDescription(int c, Accessible a) {
2336                 }
2337 
2338                 /**
2339                  * Returns a boolean value indicating whether the accessible at
2340                  * a specified row and column is selected.
2341                  *
2342                  * @param r zero-based row of the table
2343                  * @param c zero-based column of the table
2344                  * @return the boolean value true if the accessible at the
2345                  * row and column is selected. Otherwise, the boolean value
2346                  * false
2347                  */
2348                 public boolean isAccessibleSelected(int r, int c) {
2349                     return false;
2350                 }
2351 
2352                 /**
2353                  * Returns a boolean value indicating whether the specified row
2354                  * is selected.
2355                  *
2356                  * @param r zero-based row of the table
2357                  * @return the boolean value true if the specified row is selected.
2358                  * Otherwise, false.
2359                  */
2360                 public boolean isAccessibleRowSelected(int r) {
2361                     return false;
2362                 }
2363 
2364                 /**
2365                  * Returns a boolean value indicating whether the specified column
2366                  * is selected.
2367                  *
2368                  * @param c zero-based column of the table
2369                  * @return the boolean value true if the specified column is selected.
2370                  * Otherwise, false.
2371                  */
2372                 public boolean isAccessibleColumnSelected(int c) {
2373                     return false;
2374                 }
2375 
2376                 /**
2377                  * Returns the selected rows in a table.
2378                  *
2379                  * @return an array of selected rows where each element is a
2380                  * zero-based row of the table
2381                  */
2382                 public int [] getSelectedAccessibleRows() {
2383                     return new int [0];
2384                 }
2385 
2386                 /**
2387                  * Returns the selected columns in a table.
2388                  *
2389                  * @return an array of selected columns where each element is a
2390                  * zero-based column of the table
2391                  */
2392                 public int [] getSelectedAccessibleColumns() {
2393                     return new int [0];
2394                 }
2395             }
2396         } // ... end AccessibleHeadersTable
2397 
2398         /*
2399          * ElementInfo for table rows
2400          */
2401         private class TableRowElementInfo extends ElementInfo {
2402 
2403             private TableElementInfo parent;
2404             private int rowNumber;
2405 
2406             TableRowElementInfo(Element e, TableElementInfo parent, int rowNumber) {
2407                 super(e, parent);
2408                 this.parent = parent;
2409                 this.rowNumber = rowNumber;
2410             }
2411 
2412             protected void loadChildren(Element e) {
2413                 for (int x = 0; x < e.getElementCount(); x++) {
2414                     AttributeSet attrs = e.getElement(x).getAttributes();
2415 
2416                     if (attrs.getAttribute(StyleConstants.NameAttribute) ==
2417                             HTML.Tag.TH) {
2418                         TableCellElementInfo headerElementInfo =
2419                             new TableCellElementInfo(e.getElement(x), this, true);
2420                         addChild(headerElementInfo);
2421 
2422                         AccessibleTable at =
2423                             parent.getAccessibleContext().getAccessibleTable();
2424                         TableAccessibleContext tableElement =
2425                             (TableAccessibleContext)at;
2426                         tableElement.addRowHeader(headerElementInfo, rowNumber);
2427 
2428                     } else if (attrs.getAttribute(StyleConstants.NameAttribute) ==
2429                             HTML.Tag.TD) {
2430                         addChild(new TableCellElementInfo(e.getElement(x), this,
2431                                                           false));
2432                     }
2433                 }
2434             }
2435 
2436             /**
2437              * Returns the max of the rowspans of the cells in this row.
2438              */
2439             public int getRowCount() {
2440                 int rowCount = 1;
2441                 if (validateIfNecessary()) {
2442                     for (int counter = 0; counter < getChildCount();
2443                          counter++) {
2444 
2445                         TableCellElementInfo cell = (TableCellElementInfo)
2446                                                     getChild(counter);
2447 
2448                         if (cell.validateIfNecessary()) {
2449                             rowCount = Math.max(rowCount, cell.getRowCount());
2450                         }
2451                     }
2452                 }
2453                 return rowCount;
2454             }
2455 
2456             /**
2457              * Returns the sum of the column spans of the individual
2458              * cells in this row.
2459              */
2460             public int getColumnCount() {
2461                 int colCount = 0;
2462                 if (validateIfNecessary()) {
2463                     for (int counter = 0; counter < getChildCount();
2464                          counter++) {
2465                         TableCellElementInfo cell = (TableCellElementInfo)
2466                                                     getChild(counter);
2467 
2468                         if (cell.validateIfNecessary()) {
2469                             colCount += cell.getColumnCount();
2470                         }
2471                     }
2472                 }
2473                 return colCount;
2474             }
2475 
2476             /**
2477              * Overriden to invalidate the table as well as
2478              * TableRowElementInfo.
2479              */
2480             protected void invalidate(boolean first) {
2481                 super.invalidate(first);
2482                 getParent().invalidate(true);
2483             }
2484 
2485             /**
2486              * Places the TableCellElementInfos for this element in
2487              * the grid.
2488              */
2489             private void updateGrid(int row) {
2490                 if (validateIfNecessary()) {
2491                     boolean emptyRow = false;
2492 
2493                     while (!emptyRow) {
2494                         for (int counter = 0; counter < grid[row].length;
2495                                  counter++) {
2496                             if (grid[row][counter] == null) {
2497                                 emptyRow = true;
2498                                 break;
2499                             }
2500                         }
2501                         if (!emptyRow) {
2502                             row++;
2503                         }
2504                     }
2505                     for (int col = 0, counter = 0; counter < getChildCount();
2506                              counter++) {
2507                         TableCellElementInfo cell = (TableCellElementInfo)
2508                                                     getChild(counter);
2509 
2510                         while (grid[row][col] != null) {
2511                             col++;
2512                         }
2513                         for (int rowCount = cell.getRowCount() - 1;
2514                              rowCount >= 0; rowCount--) {
2515                             for (int colCount = cell.getColumnCount() - 1;
2516                                  colCount >= 0; colCount--) {
2517                                 grid[row + rowCount][col + colCount] = cell;
2518                             }
2519                         }
2520                         col += cell.getColumnCount();
2521                     }
2522                 }
2523             }
2524 
2525             /**
2526              * Returns the column count of the number of columns that have
2527              * a rowcount >= rowspan.
2528              */
2529             private int getColumnCount(int rowspan) {
2530                 if (validateIfNecessary()) {
2531                     int cols = 0;
2532                     for (int counter = 0; counter < getChildCount();
2533                          counter++) {
2534                         TableCellElementInfo cell = (TableCellElementInfo)
2535                                                     getChild(counter);
2536 
2537                         if (cell.getRowCount() >= rowspan) {
2538                             cols += cell.getColumnCount();
2539                         }
2540                     }
2541                     return cols;
2542                 }
2543                 return 0;
2544             }
2545         }
2546 
2547         /**
2548          * TableCellElementInfo is used to represents the cells of
2549          * the table.
2550          */
2551         private class TableCellElementInfo extends ElementInfo {
2552 
2553             private Accessible accessible;
2554             private boolean isHeaderCell;
2555 
2556             TableCellElementInfo(Element e, ElementInfo parent) {
2557                 super(e, parent);
2558                 this.isHeaderCell = false;
2559             }
2560 
2561             TableCellElementInfo(Element e, ElementInfo parent,
2562                                  boolean isHeaderCell) {
2563                 super(e, parent);
2564                 this.isHeaderCell = isHeaderCell;
2565             }
2566 
2567             /*
2568              * Returns whether this table cell is a header
2569              */
2570             public boolean isHeaderCell() {
2571                 return this.isHeaderCell;
2572             }
2573 
2574             /*
2575              * Returns the Accessible representing this table cell
2576              */
2577             public Accessible getAccessible() {
2578                 accessible = null;
2579                 getAccessible(this);
2580                 return accessible;
2581             }
2582 
2583             /*
2584              * Gets the outermost Accessible in the table cell
2585              */
2586             private void getAccessible(ElementInfo elementInfo) {
2587                 if (elementInfo instanceof Accessible) {
2588                     accessible = (Accessible)elementInfo;
2589                 } else {
2590                     for (int i = 0; i < elementInfo.getChildCount(); i++) {
2591                         getAccessible(elementInfo.getChild(i));
2592                     }
2593                 }
2594             }
2595 
2596             /**
2597              * Returns the rowspan attribute.
2598              */
2599             public int getRowCount() {
2600                 if (validateIfNecessary()) {
2601                     return Math.max(1, getIntAttr(getAttributes(),
2602                                                   HTML.Attribute.ROWSPAN, 1));
2603                 }
2604                 return 0;
2605             }
2606 
2607             /**
2608              * Returns the colspan attribute.
2609              */
2610             public int getColumnCount() {
2611                 if (validateIfNecessary()) {
2612                     return Math.max(1, getIntAttr(getAttributes(),
2613                                                   HTML.Attribute.COLSPAN, 1));
2614                 }
2615                 return 0;
2616             }
2617 
2618             /**
2619              * Overriden to invalidate the TableRowElementInfo as well as
2620              * the TableCellElementInfo.
2621              */
2622             protected void invalidate(boolean first) {
2623                 super.invalidate(first);
2624                 getParent().invalidate(true);
2625             }
2626         }
2627     }
2628 
2629 
2630     /**
2631      * ElementInfo provides a slim down view of an Element.  Each ElementInfo
2632      * can have any number of child ElementInfos that are not necessarily
2633      * direct children of the Element. As the Document changes various
2634      * ElementInfos become invalidated. Before accessing a particular portion
2635      * of an ElementInfo you should make sure it is valid by invoking
2636      * <code>validateIfNecessary</code>, this will return true if
2637      * successful, on the other hand a false return value indicates the
2638      * ElementInfo is not valid and can never become valid again (usually
2639      * the result of the Element the ElementInfo encapsulates being removed).
2640      */
2641     private class ElementInfo {
2642 
2643         /**
2644          * The children of this ElementInfo.
2645          */
2646         private ArrayList<ElementInfo> children;
2647         /**
2648          * The Element this ElementInfo is providing information for.
2649          */
2650         private Element element;
2651         /**
2652          * The parent ElementInfo, will be null for the root.
2653          */
2654         private ElementInfo parent;
2655         /**
2656          * Indicates the validity of the ElementInfo.
2657          */
2658         private boolean isValid;
2659         /**
2660          * Indicates if the ElementInfo can become valid.
2661          */
2662         private boolean canBeValid;
2663 
2664 
2665         /**
2666          * Creates the root ElementInfo.
2667          */
2668         ElementInfo(Element element) {
2669             this(element, null);
2670         }
2671 
2672         /**
2673          * Creates an ElementInfo representing <code>element</code> with
2674          * the specified parent.
2675          */
2676         ElementInfo(Element element, ElementInfo parent) {
2677             this.element = element;
2678             this.parent = parent;
2679             isValid = false;
2680             canBeValid = true;
2681         }
2682 
2683         /**
2684          * Validates the receiver. This recreates the children as well. This
2685          * will be invoked within a <code>readLock</code>. If this is overriden
2686          * it MUST invoke supers implementation first!
2687          */
2688         protected void validate() {
2689             isValid = true;
2690             loadChildren(getElement());
2691         }
2692 
2693         /**
2694          * Recreates the direct children of <code>info</code>.
2695          */
2696         protected void loadChildren(Element parent) {
2697             if (!parent.isLeaf()) {
2698                 for (int counter = 0, maxCounter = parent.getElementCount();
2699                     counter < maxCounter; counter++) {
2700                     Element e = parent.getElement(counter);
2701                     ElementInfo childInfo = createElementInfo(e, this);
2702 
2703                     if (childInfo != null) {
2704                         addChild(childInfo);
2705                     }
2706                     else {
2707                         loadChildren(e);
2708                     }
2709                 }
2710             }
2711         }
2712 
2713         /**
2714          * Returns the index of the child in the parent, or -1 for the
2715          * root or if the parent isn't valid.
2716          */
2717         public int getIndexInParent() {
2718             if (parent == null || !parent.isValid()) {
2719                 return -1;
2720             }
2721             return parent.indexOf(this);
2722         }
2723 
2724         /**
2725          * Returns the Element this <code>ElementInfo</code> represents.
2726          */
2727         public Element getElement() {
2728             return element;
2729         }
2730 
2731         /**
2732          * Returns the parent of this Element, or null for the root.
2733          */
2734         public ElementInfo getParent() {
2735             return parent;
2736         }
2737 
2738         /**
2739          * Returns the index of the specified child, or -1 if
2740          * <code>child</code> isn't a valid child.
2741          */
2742         public int indexOf(ElementInfo child) {
2743             ArrayList children = this.children;
2744 
2745             if (children != null) {
2746                 return children.indexOf(child);
2747             }
2748             return -1;
2749         }
2750 
2751         /**
2752          * Returns the child ElementInfo at <code>index</code>, or null
2753          * if <code>index</code> isn't a valid index.
2754          */
2755         public ElementInfo getChild(int index) {
2756             if (validateIfNecessary()) {
2757                 ArrayList<ElementInfo> children = this.children;
2758 
2759                 if (children != null && index >= 0 &&
2760                                         index < children.size()) {
2761                     return children.get(index);
2762                 }
2763             }
2764             return null;
2765         }
2766 
2767         /**
2768          * Returns the number of children the ElementInfo contains.
2769          */
2770         public int getChildCount() {
2771             validateIfNecessary();
2772             return (children == null) ? 0 : children.size();
2773         }
2774 
2775         /**
2776          * Adds a new child to this ElementInfo.
2777          */
2778         protected void addChild(ElementInfo child) {
2779             if (children == null) {
2780                 children = new ArrayList<ElementInfo>();
2781             }
2782             children.add(child);
2783         }
2784 
2785         /**
2786          * Returns the View corresponding to this ElementInfo, or null
2787          * if the ElementInfo can't be validated.
2788          */
2789         protected View getView() {
2790             if (!validateIfNecessary()) {
2791                 return null;
2792             }
2793             Object lock = lock();
2794             try {
2795                 View rootView = getRootView();
2796                 Element e = getElement();
2797                 int start = e.getStartOffset();
2798 
2799                 if (rootView != null) {
2800                     return getView(rootView, e, start);
2801                 }
2802                 return null;
2803             } finally {
2804                 unlock(lock);
2805             }
2806         }
2807 
2808         /**
2809          * Returns the Bounds for this ElementInfo, or null
2810          * if the ElementInfo can't be validated.
2811          */
2812         public Rectangle getBounds() {
2813             if (!validateIfNecessary()) {
2814                 return null;
2815             }
2816             Object lock = lock();
2817             try {
2818                 Rectangle bounds = getRootEditorRect();
2819                 View rootView = getRootView();
2820                 Element e = getElement();
2821 
2822                 if (bounds != null && rootView != null) {
2823                     try {
2824                         return rootView.modelToView(e.getStartOffset(),
2825                                                     Position.Bias.Forward,
2826                                                     e.getEndOffset(),
2827                                                     Position.Bias.Backward,
2828                                                     bounds).getBounds();
2829                     } catch (BadLocationException ble) { }
2830                 }
2831             } finally {
2832                 unlock(lock);
2833             }
2834             return null;
2835         }
2836 
2837         /**
2838          * Returns true if this ElementInfo is valid.
2839          */
2840         protected boolean isValid() {
2841             return isValid;
2842         }
2843 
2844         /**
2845          * Returns the AttributeSet associated with the Element, this will
2846          * return null if the ElementInfo can't be validated.
2847          */
2848         protected AttributeSet getAttributes() {
2849             if (validateIfNecessary()) {
2850                 return getElement().getAttributes();
2851             }
2852             return null;
2853         }
2854 
2855         /**
2856          * Returns the AttributeSet associated with the View that is
2857          * representing this Element, this will
2858          * return null if the ElementInfo can't be validated.
2859          */
2860         protected AttributeSet getViewAttributes() {
2861             if (validateIfNecessary()) {
2862                 View view = getView();
2863 
2864                 if (view != null) {
2865                     return view.getElement().getAttributes();
2866                 }
2867                 return getElement().getAttributes();
2868             }
2869             return null;
2870         }
2871 
2872         /**
2873          * Convenience method for getting an integer attribute from the passed
2874          * in AttributeSet.
2875          */
2876         protected int getIntAttr(AttributeSet attrs, Object key, int deflt) {
2877             if (attrs != null && attrs.isDefined(key)) {
2878                 int i;
2879                 String val = (String)attrs.getAttribute(key);
2880                 if (val == null) {
2881                     i = deflt;
2882                 }
2883                 else {
2884                     try {
2885                         i = Math.max(0, Integer.parseInt(val));
2886                     } catch (NumberFormatException x) {
2887                         i = deflt;
2888                     }
2889                 }
2890                 return i;
2891             }
2892             return deflt;
2893         }
2894 
2895         /**
2896          * Validates the ElementInfo if necessary.  Some ElementInfos may
2897          * never be valid again.  You should check <code>isValid</code> before
2898          * using one.  This will reload the children and invoke
2899          * <code>validate</code> if the ElementInfo is invalid and can become
2900          * valid again. This will return true if the receiver is valid.
2901          */
2902         protected boolean validateIfNecessary() {
2903             if (!isValid() && canBeValid) {
2904                 children = null;
2905                 Object lock = lock();
2906 
2907                 try {
2908                     validate();
2909                 } finally {
2910                     unlock(lock);
2911                 }
2912             }
2913             return isValid();
2914         }
2915 
2916         /**
2917          * Invalidates the ElementInfo. Subclasses should override this
2918          * if they need to reset state once invalid.
2919          */
2920         protected void invalidate(boolean first) {
2921             if (!isValid()) {
2922                 if (canBeValid && !first) {
2923                     canBeValid = false;
2924                 }
2925                 return;
2926             }
2927             isValid = false;
2928             canBeValid = first;
2929             if (children != null) {
2930                 for (ElementInfo child : children) {
2931                     child.invalidate(false);
2932                 }
2933                 children = null;
2934             }
2935         }
2936 
2937         private View getView(View parent, Element e, int start) {
2938             if (parent.getElement() == e) {
2939                 return parent;
2940             }
2941             int index = parent.getViewIndex(start, Position.Bias.Forward);
2942 
2943             if (index != -1 && index < parent.getViewCount()) {
2944                 return getView(parent.getView(index), e, start);
2945             }
2946             return null;
2947         }
2948 
2949         private int getClosestInfoIndex(int index) {
2950             for (int counter = 0; counter < getChildCount(); counter++) {
2951                 ElementInfo info = getChild(counter);
2952 
2953                 if (index < info.getElement().getEndOffset() ||
2954                     index == info.getElement().getStartOffset()) {
2955                     return counter;
2956                 }
2957             }
2958             return -1;
2959         }
2960 
2961         private void update(DocumentEvent e) {
2962             if (!isValid()) {
2963                 return;
2964             }
2965             ElementInfo parent = getParent();
2966             Element element = getElement();
2967 
2968             do {
2969                 DocumentEvent.ElementChange ec = e.getChange(element);
2970                 if (ec != null) {
2971                     if (element == getElement()) {
2972                         // One of our children changed.
2973                         invalidate(true);
2974                     }
2975                     else if (parent != null) {
2976                         parent.invalidate(parent == getRootInfo());
2977                     }
2978                     return;
2979                 }
2980                 element = element.getParentElement();
2981             } while (parent != null && element != null &&
2982                      element != parent.getElement());
2983 
2984             if (getChildCount() > 0) {
2985                 Element elem = getElement();
2986                 int pos = e.getOffset();
2987                 int index0 = getClosestInfoIndex(pos);
2988                 if (index0 == -1 &&
2989                     e.getType() == DocumentEvent.EventType.REMOVE &&
2990                     pos >= elem.getEndOffset()) {
2991                     // Event beyond our offsets. We may have represented this,
2992                     // that is the remove may have removed one of our child
2993                     // Elements that represented this, so, we should foward
2994                     // to last element.
2995                     index0 = getChildCount() - 1;
2996                 }
2997                 ElementInfo info = (index0 >= 0) ? getChild(index0) : null;
2998                 if (info != null &&
2999                     (info.getElement().getStartOffset() == pos) && (pos > 0)) {
3000                     // If at a boundary, forward the event to the previous
3001                     // ElementInfo too.
3002                     index0 = Math.max(index0 - 1, 0);
3003                 }
3004                 int index1;
3005                 if (e.getType() != DocumentEvent.EventType.REMOVE) {
3006                     index1 = getClosestInfoIndex(pos + e.getLength());
3007                     if (index1 < 0) {
3008                         index1 = getChildCount() - 1;
3009                     }
3010                 }
3011                 else {
3012                     index1 = index0;
3013                     // A remove may result in empty elements.
3014                     while ((index1 + 1) < getChildCount() &&
3015                            getChild(index1 + 1).getElement().getEndOffset() ==
3016                            getChild(index1 + 1).getElement().getStartOffset()){
3017                         index1++;
3018                     }
3019                 }
3020                 index0 = Math.max(index0, 0);
3021                 // The check for isValid is here as in the process of
3022                 // forwarding update our child may invalidate us.
3023                 for (int i = index0; i <= index1 && isValid(); i++) {
3024                     getChild(i).update(e);
3025                 }
3026             }
3027         }
3028     }
3029 
3030     /**
3031      * DocumentListener installed on the current Document.  Will invoke
3032      * <code>update</code> on the <code>RootInfo</code> in response to
3033      * any event.
3034      */
3035     private class DocumentHandler implements DocumentListener {
3036         public void insertUpdate(DocumentEvent e) {
3037             getRootInfo().update(e);
3038         }
3039         public void removeUpdate(DocumentEvent e) {
3040             getRootInfo().update(e);
3041         }
3042         public void changedUpdate(DocumentEvent e) {
3043             getRootInfo().update(e);
3044         }
3045     }
3046 
3047     /*
3048      * PropertyChangeListener installed on the editor.
3049      */
3050     private class PropertyChangeHandler implements PropertyChangeListener {
3051         public void propertyChange(PropertyChangeEvent evt) {
3052             if (evt.getPropertyName().equals("document")) {
3053                 // handle the document change
3054                 setDocument(editor.getDocument());
3055             }
3056         }
3057     }
3058 }